package shape3d;

import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.geom.util.AffineTransformation;
import com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier;
import com.vividsolutions.jts.triangulate.DelaunayTriangulationBuilder;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.jme3.math.Triangle;
import com.vividsolutions.jts.geom.util.LinearComponentExtracter;


public class ShapeUtilities
{
   
   private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(new PrecisionModel());

   public void ShapeUtilities(){
   };

   public static MeshData createMeshFromJtsGeometry(Geometry geom)
   {
      MeshData mesh = createShape(geom);
      return mesh;
   }
   public static Geometry createJtsGeometry(ArrayList<Geometry> lines,float lineWidth){
      Geometry[] a = lines.toArray(new Geometry[lines.size()]);
      GeometryCollection collection = new GeometryCollection(a,GEOMETRY_FACTORY);
      Geometry geom = combineComponents(collection, lineWidth);
      geom = simplify(geom, (int) lineWidth);
      return geom;
   }
   
   public static com.jme3.scene.Geometry createShapeFromPoints(ArrayList<Geometry> lines,float lineWidth,String name)
   {
      MeshData mesh = createMeshFromJtsGeometry(createJtsGeometry(lines,lineWidth));
      com.jme3.scene.Geometry g = new com.jme3.scene.Geometry(name, mesh.createMesh());
      return g;

   }   
   public static MeshData createShape(Geometry geom)
   {
      Geometry polygon = ShapeUtilities.triangulate(geom);
      ArrayList<Triangle> triangles = getTriangles(polygon);
      ArrayList<LineString> lines = getEdges(geom);

      int numOfIndexesInTriangle = 3;
      int p = triangles.size() * numOfIndexesInTriangle;
      int indexSize = 2 * p + lines.size() * 6;
      float[] vertexArray = new float[indexSize * 3];
      float[] normalArray = new float[indexSize * 3];
      float[] uvArray = new float[indexSize * 2];
      short[] indexArray = new short[indexSize];
      //Vector3f neg = Vector3f.UNIT_Y.mult(-1f);

      int index = 0;
      for (Triangle t : triangles)
      {
         Vector3f t1 = t.get1();
         Vector3f t2 = t.get2();
         Vector3f t3 = t.get3();

         addTriangle(new Triangle(t1.add(0, FastMath.ZERO_TOLERANCE, 0), t2.add(0, FastMath.ZERO_TOLERANCE, 0), t3.add(0, FastMath.ZERO_TOLERANCE, 0)), Vector3f.UNIT_Y, index, vertexArray, normalArray, uvArray, indexArray);
         index += 3;
     }

      return new MeshData(vertexArray, normalArray, uvArray, indexArray);
   }



   public static Point createPoint(Coordinate c)
   {
      return GEOMETRY_FACTORY.createPoint(c);
   }

   /** Combines Geometry. Points are converted into circle with size "pointSize" and then combined. */
   public static Geometry combineComponents(Geometry g, double pointSize)
   {

      return componentBuffers(g, pointSize).union();
   }

   public static Geometry simplify(Geometry g, float amount)
   {
      return DouglasPeuckerSimplifier.simplify(g, amount);
   }

   public static Geometry intersection(Geometry a, Geometry b)
   {
      return a.intersection(b);
   }

   public static Geometry union(Geometry a, Geometry b)
   {
      return a.union(b);
   }

   public static Geometry symDifference(Geometry a, Geometry b)
   {
      return a.symDifference(b);
   }

   public static Geometry difference(Geometry a, Geometry b)
   {
      return a.difference(b);
   }

   public static Geometry triangulate(Geometry geom)
   {
      DelaunayTriangulationBuilder builder = new DelaunayTriangulationBuilder();
      builder.setSites(geom);
      builder.setTolerance(0);
      Geometry tris = builder.getTriangles(geom.getFactory());
      return intersection(tris, geom);
   }

   public static Coordinate getCenter(Geometry g)
   {
      return g.getEnvelopeInternal().centre();
   }

   public static Geometry translateCentreToOrigin(Geometry g)
   {
      Coordinate centre = getCenter(g);
      AffineTransformation trans = AffineTransformation.translationInstance(-centre.x, -centre.y);
      return trans.transform(g);
   }



   public static ArrayList<Triangle> getTriangles(Geometry geom)
   {
      ArrayList<Triangle> triangles = new ArrayList<Triangle>();

      LinkedList<Geometry> gc = new LinkedList<Geometry>();
      gc.add(geom);

      while (!gc.isEmpty())
      {
         Geometry g = gc.pop();

         if (g instanceof GeometryCollection)
         {
            GeometryCollection collection = (GeometryCollection) g;
            for (int i = 0; i < collection.getNumGeometries(); i++) gc.add(collection.getGeometryN(i));
         }
         else if (g instanceof Polygon)
         {
            Polygon p = (Polygon) g;
            Coordinate[] cords = p.getCoordinates();
            triangles.add(new Triangle(toVector3f(cords[0]), toVector3f(cords[1]), toVector3f(cords[2])));

            if (g.getNumPoints() == 5) //when triangulation failed.
            {
               triangles.add(new Triangle(toVector3f(cords[2]), toVector3f(cords[3]), toVector3f(cords[0])));
               if (!cords[4].equals2D(cords[0])) triangles.add(new Triangle(toVector3f(cords[3]), toVector3f(cords[4]), toVector3f(cords[0])));
            }
            if (g.getNumPoints() == 6)
            {
               triangles.add(new Triangle(toVector3f(cords[2]), toVector3f(cords[3]), toVector3f(cords[4])));
               triangles.add(new Triangle(toVector3f(cords[4]), toVector3f(cords[5]), toVector3f(cords[2])));
               if (!cords[5].equals2D(cords[0])) triangles.add(new Triangle(toVector3f(cords[5]), toVector3f(cords[0]), toVector3f(cords[2])));
            }
         }
      }
      return triangles;
   }

   public static Vector3f toVector3f(Coordinate c)
   {
      return new Vector3f((float) c.x, 0, (float) c.y);
   }

   public static Vector3f toVector3f(Point c)
   {
      return toVector3f(c.getCoordinate());
   }
   public static Point toPoint(Vector3f p)
   {
       return createPoint(new Coordinate(p.x,p.z));
   }
   /** Extracts all edges of target geometry.*/
   
   public static ArrayList<LineString> getEdges(Geometry geom)
   {
      @SuppressWarnings("unchecked")
      List<LineString> lines = LinearComponentExtracter.getLines(geom);
      ArrayList<LineString> segments = new ArrayList<LineString>(lines.size());
      for (Iterator<LineString> it = lines.iterator(); it.hasNext();)
      {
         LineString line = it.next();
         for (int i = 1; i < line.getNumPoints(); i++)
         {
            LineString seg = GEOMETRY_FACTORY.createLineString(
                    new Coordinate[]
                    {
                       line.getCoordinateN(i - 1), line.getCoordinateN(i)
                    });
            segments.add(seg);
         }
      }
      return segments;
   }

   private static GeometryCollection componentBuffers(Geometry g, double distance)
   {
      List<Geometry> bufs = new ArrayList<Geometry>();
      for (Iterator it = new GeometryCollectionIterator(g); it.hasNext();)
      {
         Geometry comp = (Geometry) it.next();
         if (comp instanceof GeometryCollection) continue;
         bufs.add(comp.buffer(distance));
      }
      
      return GEOMETRY_FACTORY.createGeometryCollection(GeometryFactory.toGeometryArray(bufs));
   }

   private static void addTriangle(Triangle t, Vector3f normal, int index, float[] vertexArray, float[] normalArray, float[] uvArray, short[] indexArray)
   {
      addCord(t.get1(), normal, index, vertexArray, normalArray, uvArray, indexArray);
      addCord(t.get2(), normal, index + 1, vertexArray, normalArray, uvArray, indexArray);
      addCord(t.get3(), normal, index + 2, vertexArray, normalArray, uvArray, indexArray);
   }
   private static void addCord(Vector3f triangle, Vector3f normal, int index, float[] vertexArray, float[] normalArray, float[] uvArray, short[] indexArray)
   {
      indexArray[index] = (short) index;
      setInArray(triangle, vertexArray, index);
      setInArray(normal, normalArray, index);
      setInArray(new Vector2f(triangle.getX(), triangle.getZ()), uvArray, index);
   }
   public static void setInArray(Vector3f p1, float[] array, int i)
   {
      array[i * 3] = p1.x;
      array[i * 3 + 1] = p1.y;
      array[i * 3 + 2] = p1.z;
   }
   public static void setInArray(Vector2f p1, float[] array, int i)
   {
      array[i * 2] = p1.x;
      array[i * 2 + 1] = p1.y;
   }   
}
